/**
 * Copyright (c) HashiCorp, Inc.
 * SPDX-License-Identifier: BUSL-1.1
 */

import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support';
import clientsHandler, {
  STATIC_NOW,
  LICENSE_START,
  UPGRADE_DATE,
  STATIC_PREVIOUS_MONTH,
} from 'vault/mirage/handlers/clients';
import syncHandler from 'vault/mirage/handlers/sync';
import sinon from 'sinon';
import { visit, click, findAll, fillIn, currentURL } from '@ember/test-helpers';
import { login } from 'vault/tests/helpers/auth/auth-helpers';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
import { CHARTS, CLIENT_COUNT, FILTERS } from 'vault/tests/helpers/clients/client-count-selectors';
import timestamp from 'core/utils/timestamp';
import { format } from 'date-fns';
import { ACTIVITY_RESPONSE_STUB } from 'vault/tests/helpers/clients/client-count-helpers';
import { ClientFilters, flattenMounts } from 'core/utils/client-count-utils';

module('Acceptance | clients | overview', function (hooks) {
  setupApplicationTest(hooks);
  setupMirage(hooks);

  hooks.beforeEach(async function () {
    sinon.replace(timestamp, 'now', sinon.fake.returns(STATIC_NOW));
    clientsHandler(this.server);
    this.store = this.owner.lookup('service:store');
  });

  test('it should hide secrets sync stats when feature is NOT on license', async function (assert) {
    // mocks endpoint for no additional license modules
    this.server.get('/sys/license/features', () => ({ features: [] }));

    await login();
    await visit('/vault/clients/counts/overview');
    assert.dom(CLIENT_COUNT.statTextValue('Secret sync')).doesNotExist();
    assert.dom(CLIENT_COUNT.statTextValue('Entity')).exists('other stats are still visible');
    await click(GENERAL.inputByAttr('toggle view'));
    assert.dom(CHARTS.legend).hasText('Entity clients Non-entity clients ACME clients');
  });

  // These tests use the clientsHandler which dynamically generates activity data, used for asserting date querying, etc
  module('dynamic data', function (hooks) {
    hooks.beforeEach(async function () {
      // stub secrets sync being activated
      this.server.get('/sys/activation-flags', function () {
        return {
          data: {
            activated: ['secrets-sync'],
            unactivated: [],
          },
        };
      });

      this.activity = await this.store.findRecord('clients/activity', 'some-activity-id');
      this.mostRecentMonth = this.activity.byMonth[this.activity.byMonth.length - 1];
      await login();
      return visit('/vault/clients/counts/overview');
    });

    test('it should render charts', async function (assert) {
      assert
        .dom(`${GENERAL.flashMessage}.is-info`)
        .includesText(
          'counts returned in this usage period are an estimate',
          'Shows warning from API about client count estimations'
        );
      assert
        .dom(CLIENT_COUNT.dateRange.dateDisplay('start'))
        .hasText('July 2023', 'billing start month is correctly parsed from license');
      assert
        .dom(CLIENT_COUNT.dateRange.dateDisplay('end'))
        .hasText('January 2024', 'billing start month is correctly parsed from license');
      assert
        .dom(CLIENT_COUNT.card('Client usage trends for selected billing period'))
        .exists('Shows running totals with monthly breakdown charts');
      assert
        .dom(`${CLIENT_COUNT.card('Client usage trends for selected billing period')} ${CHARTS.xAxisLabel}`)
        .hasText('7/23', 'x-axis labels start with billing start date');
      assert.dom(CHARTS.xAxisLabel).exists({ count: 7 }, 'chart months matches query');
    });

    test('it should update charts when querying date ranges', async function (assert) {
      // query for single, historical month with no new counts (July 2023), which means there is no monthly breakdown
      const service = this.owner.lookup('service:version');
      service.type = 'community';

      const licenseStartMonth = format(LICENSE_START, 'yyyy-MM');
      const upgradeMonth = format(UPGRADE_DATE, 'yyyy-MM');
      const endMonth = format(STATIC_PREVIOUS_MONTH, 'yyyy-MM');
      await click(CLIENT_COUNT.dateRange.edit);
      await fillIn(CLIENT_COUNT.dateRange.editDate('start'), licenseStartMonth);
      await fillIn(CLIENT_COUNT.dateRange.editDate('end'), licenseStartMonth);

      await click(GENERAL.submitButton);
      assert
        .dom(CLIENT_COUNT.usageStats('Client usage'))
        .exists('running total single month usage stats show');
      assert
        .dom(CLIENT_COUNT.card('Client usage trends for selected billing period'))
        .doesNotExist('running total month over month charts do not show');

      // change to start on month/year of upgrade to 1.10
      await click(CLIENT_COUNT.dateRange.edit);
      await fillIn(CLIENT_COUNT.dateRange.editDate('start'), upgradeMonth);
      await fillIn(CLIENT_COUNT.dateRange.editDate('end'), endMonth);
      await click(GENERAL.submitButton);
      assert
        .dom(CLIENT_COUNT.dateRange.dateDisplay('start'))
        .hasText('September 2023', 'billing start month is correctly parsed from license');
      assert
        .dom(CLIENT_COUNT.card('Client usage trends for selected billing period'))
        .exists('Shows running totals with monthly breakdown charts');
      assert
        .dom(`${CLIENT_COUNT.card('Client usage trends for selected billing period')} ${CHARTS.xAxisLabel}`)
        .hasText('9/23', 'x-axis labels start with queried start month (upgrade date)');
      assert.dom(CHARTS.xAxisLabel).exists({ count: 4 }, 'chart months matches query');

      // query for single, historical month (upgrade month)
      await click(CLIENT_COUNT.dateRange.edit);
      await fillIn(CLIENT_COUNT.dateRange.editDate('start'), upgradeMonth);
      await fillIn(CLIENT_COUNT.dateRange.editDate('end'), upgradeMonth);
      await click(GENERAL.submitButton);
      assert
        .dom(CLIENT_COUNT.card('Client usage trends for selected billing period'))
        .exists('running total month over month charts show');

      // query historical date range (from September 2023 to December 2023)
      await click(CLIENT_COUNT.dateRange.edit);
      await fillIn(CLIENT_COUNT.dateRange.editDate('start'), '2023-09');
      await fillIn(CLIENT_COUNT.dateRange.editDate('end'), '2023-12');
      await click(GENERAL.submitButton);

      assert
        .dom(CLIENT_COUNT.dateRange.dateDisplay('start'))
        .hasText('September 2023', 'billing start month is correctly parsed from license');
      assert
        .dom(CLIENT_COUNT.dateRange.dateDisplay('end'))
        .hasText('December 2023', 'billing start month is correctly parsed from license');
      assert
        .dom(CLIENT_COUNT.card('Client usage trends for selected billing period'))
        .exists('Shows running totals with monthly breakdown charts');

      assert.dom(CHARTS.xAxisLabel).exists({ count: 4 }, 'chart months matches query');
      const xAxisLabels = findAll(CHARTS.xAxisLabel);
      assert
        .dom(xAxisLabels[xAxisLabels.length - 1])
        .hasText('12/23', 'x-axis labels end with queried end month');
    });
  });

  // * FILTERING ASSERTIONS
  // These tests use the static data from the ACTIVITY_RESPONSE_STUB to assert filtering
  // Filtering tests are split between integration and acceptance tests
  // because changing filters updates the URL query params.
  module('static data', function (hooks) {
    hooks.beforeEach(async function () {
      this.server.get('sys/internal/counters/activity', () => {
        return {
          request_id: 'some-activity-id',
          data: ACTIVITY_RESPONSE_STUB,
        };
      });
      const staticActivity = await this.store.findRecord('clients/activity', 'some-activity-id');
      this.staticMostRecentMonth = staticActivity.byMonth[staticActivity.byMonth.length - 1];
      await login();
      return visit('/vault/clients/counts/overview');
    });

    test('it filters attribution table when filters are applied', async function (assert) {
      const url = '/vault/clients/counts/overview';
      const topMount = flattenMounts(this.staticMostRecentMonth.new_clients.namespaces)[0];
      const { namespace_path, mount_type, mount_path } = topMount;
      assert.strictEqual(currentURL(), url, 'URL does not contain query params');
      await fillIn(GENERAL.selectByAttr('attribution-month'), this.staticMostRecentMonth.timestamp);
      await click(FILTERS.dropdownToggle(ClientFilters.NAMESPACE));
      await click(FILTERS.dropdownItem(namespace_path));
      await click(FILTERS.dropdownToggle(ClientFilters.MOUNT_PATH));
      await click(FILTERS.dropdownItem(mount_path));
      await click(FILTERS.dropdownToggle(ClientFilters.MOUNT_TYPE));
      await click(FILTERS.dropdownItem(mount_type));
      assert.strictEqual(
        currentURL(),
        `${url}?mount_path=${encodeURIComponent(
          mount_path
        )}&mount_type=${mount_type}&namespace_path=${namespace_path}`,
        'url query params match filters'
      );
      assert.dom(FILTERS.tag()).exists({ count: 3 }, '3 filter tags render');
      assert.dom(GENERAL.tableRow()).exists({ count: 1 }, 'it only renders the filtered table row');
      assert.dom(GENERAL.tableData(0, 'namespace_path')).hasText(namespace_path);
      assert.dom(GENERAL.tableData(0, 'mount_type')).hasText(mount_type);
      assert.dom(GENERAL.tableData(0, 'mount_path')).hasText(mount_path);
    });

    test('it updates table when filters are cleared', async function (assert) {
      const url = '/vault/clients/counts/overview';
      const mounts = flattenMounts(this.staticMostRecentMonth.new_clients.namespaces);
      const { namespace_path, mount_type, mount_path } = mounts[0];
      await fillIn(GENERAL.selectByAttr('attribution-month'), this.staticMostRecentMonth.timestamp);
      await click(FILTERS.dropdownToggle(ClientFilters.NAMESPACE));
      await click(FILTERS.dropdownItem(namespace_path));
      await click(FILTERS.dropdownToggle(ClientFilters.MOUNT_PATH));
      await click(FILTERS.dropdownItem(mount_path));
      await click(FILTERS.dropdownToggle(ClientFilters.MOUNT_TYPE));
      await click(FILTERS.dropdownItem(mount_type));
      assert.dom(GENERAL.tableRow()).exists({ count: 1 }, 'it only renders the filtered table row');
      await click(FILTERS.clearTag(namespace_path));
      assert.strictEqual(
        currentURL(),
        `${url}?mount_path=${encodeURIComponent(mount_path)}&mount_type=${mount_type}`,
        'url does not have namespace_path query param'
      );
      assert.dom(GENERAL.tableRow()).exists({ count: 2 }, 'it renders 2 data rows that match filters');
      assert.dom(GENERAL.tableData(0, 'namespace_path')).hasText('root');
      assert.dom(GENERAL.tableData(0, 'mount_type')).hasText(mount_type);
      assert.dom(GENERAL.tableData(1, 'namespace_path')).hasText('ns1');
      assert.dom(GENERAL.tableData(1, 'mount_type')).hasText(mount_type);
      assert.dom(GENERAL.tableData(1, 'mount_path')).hasText(mount_path);
      await click(GENERAL.button('Clear filters'));
      assert.strictEqual(currentURL(), url, 'url does not have any query params');
      assert
        .dom(GENERAL.tableRow())
        .exists({ count: mounts.length }, 'it renders all data when filters are cleared');
    });

    test('it clears query params when month is unselected', async function (assert) {
      const url = '/vault/clients/counts/overview';
      const mounts = flattenMounts(this.staticMostRecentMonth.new_clients.namespaces);
      const { namespace_path, mount_type, mount_path } = mounts[0];
      await fillIn(GENERAL.selectByAttr('attribution-month'), this.staticMostRecentMonth.timestamp);
      await click(FILTERS.dropdownToggle(ClientFilters.NAMESPACE));
      await click(FILTERS.dropdownItem(namespace_path));
      await click(FILTERS.dropdownToggle(ClientFilters.MOUNT_PATH));
      await click(FILTERS.dropdownItem(mount_path));
      await click(FILTERS.dropdownToggle(ClientFilters.MOUNT_TYPE));
      await click(FILTERS.dropdownItem(mount_type));
      assert.strictEqual(
        currentURL(),
        `${url}?mount_path=${encodeURIComponent(
          mount_path
        )}&mount_type=${mount_type}&namespace_path=${namespace_path}`,
        'url query params match filters'
      );
      await fillIn(GENERAL.selectByAttr('attribution-month'), '');
      assert.strictEqual(currentURL(), url, 'url query params clear when month is not selected');
    });
  });

  module('license includes secrets sync feature', function (hooks) {
    hooks.beforeEach(async function () {
      syncHandler(this.server);
    });

    test('it should show secrets sync stats when the feature is activated', async function (assert) {
      await login();
      await visit('/vault/clients/counts/overview');
      assert.dom(CLIENT_COUNT.statTextValue('Secret sync')).exists('shows secret sync data on overview');
      await click(GENERAL.inputByAttr('toggle view'));
      assert
        .dom(CHARTS.legend)
        .hasText(
          'Entity clients Non-entity clients ACME clients Secret sync clients',
          'it renders legend in order that matches the stacked bar data'
        );
    });

    test('it should hide secrets sync stats when feature is NOT activated', async function (assert) {
      this.server.get('/sys/activation-flags', () => {
        return {
          data: { activated: [], unactivated: ['secrets-sync'] },
        };
      });

      await login();
      await visit('/vault/clients/counts/overview');

      assert
        .dom(CLIENT_COUNT.statTextValue('Secret sync'))
        .doesNotExist('stat is hidden because feature is not activated');
      assert.dom(CLIENT_COUNT.statTextValue('Entity')).exists('other stats are still visible');
      await click(GENERAL.inputByAttr('toggle view'));
      assert
        .dom(CHARTS.legend)
        .hasText(
          'Entity clients Non-entity clients ACME clients',
          'it renders legend in order that matches the stacked bar data and does not include secret sync'
        );
    });

    test('it should show secrets sync stats for HVD managed clusters', async function (assert) {
      // mock HVD managed cluster
      this.owner.lookup('service:flags').featureFlags = ['VAULT_CLOUD_ADMIN_NAMESPACE'];

      await login();
      await visit('/vault/clients/counts/overview');
      assert.dom(CLIENT_COUNT.statTextValue('Secret sync')).exists();
      await click(GENERAL.inputByAttr('toggle view'));
      assert
        .dom(CHARTS.legend)
        .hasText(
          'Entity clients Non-entity clients ACME clients Secret sync clients',
          'it renders legend in order that matches the stacked bar data'
        );
    });
  });
});
